#include <mylib/device/oled.h>
#include <mylib/utility/debug.h>
#include <mylib/utility/math.h>
#include <stdint.h>

// oled写命令控制字节
#define OLED_WRITE_CMD 0x00
// oled写数据控制字节
#define OLED_WRITE_DATA 0x40

// 宏函数为了方便
// oled写命令函数
#define OLED_WRITE_CMD_FUNC(oled, cmd) \
    oled->iic_write_byte_func(oled->dev_addr, OLED_WRITE_CMD, cmd)
// oled写数据函数
#define OLED_WRITE_DATA_FUNC(oled, data) \
    oled->iic_write_byte_func(oled->dev_addr, OLED_WRITE_DATA, data)


// 调用OLED初始化之前，一定要先配置好I2C外设
void oled_init(oled_t* oled)
{
    MYLIB_DEBUG_ASSERT(oled != NULL);
    MYLIB_DEBUG_ASSERT(oled->iic_write_byte_func != NULL);
    MYLIB_DEBUG_ASSERT(oled->dev_addr != 0);

    OLED_WRITE_CMD_FUNC(oled, 0xAE);   // 关闭显示
    OLED_WRITE_CMD_FUNC(oled, 0xD5);   // 设置显示时钟分频比/振荡器频率
    OLED_WRITE_CMD_FUNC(oled, 0x80);
    OLED_WRITE_CMD_FUNC(oled, 0xA8);   // 设置多路复用率
    OLED_WRITE_CMD_FUNC(oled, 0x3F);
    OLED_WRITE_CMD_FUNC(oled, 0xD3);   // 设置显示偏移
    OLED_WRITE_CMD_FUNC(oled, 0x00);
    OLED_WRITE_CMD_FUNC(oled, 0x40);   // 设置显示开始行
    OLED_WRITE_CMD_FUNC(oled, 0xA1);   // 设置左右方向，0xA1正常 0xA0左右反置
    OLED_WRITE_CMD_FUNC(oled, 0xC8);   // 设置上下方向，0xC8正常 0xC0上下反置
    OLED_WRITE_CMD_FUNC(oled, 0xDA);   // 设置COM引脚硬件配置
    OLED_WRITE_CMD_FUNC(oled, 0x12);
    OLED_WRITE_CMD_FUNC(oled, 0x81);   // 设置对比度控制
    OLED_WRITE_CMD_FUNC(oled, 0xCF);
    OLED_WRITE_CMD_FUNC(oled, 0xD9);   // 设置预充电周期
    OLED_WRITE_CMD_FUNC(oled, 0xF1);
    OLED_WRITE_CMD_FUNC(oled, 0xDB);   // 设置VCOMH取消选择级别
    OLED_WRITE_CMD_FUNC(oled, 0x30);
    OLED_WRITE_CMD_FUNC(oled, 0xA4);   // 设置整个显示打开/关闭
    OLED_WRITE_CMD_FUNC(oled, 0xA6);   // 设置正常/倒转显示
    OLED_WRITE_CMD_FUNC(oled, 0x8D);   // 设置充电泵
    OLED_WRITE_CMD_FUNC(oled, 0x14);
    OLED_WRITE_CMD_FUNC(oled, 0xAF);   // 开启显示
    oled_clear(oled);                                                  // OLED清屏
}

void oled_set_cursor(oled_t* oled, uint8_t page, uint8_t x)
{
    // 如果使用此程序驱动1.3寸的OLED显示屏，则需要解除此注释
    // 因为1.3寸的OLED驱动芯片（SH1106）有132列
    // 屏幕的起始列接在了第2列，而不是第0列
    // 所以需要将X加2，才能正常显示
    // x += 2;

    // 通过指令设置页地址和列地址
    oled->iic_write_byte_func(
        oled->dev_addr, OLED_WRITE_CMD, 0xB0 | page);   // 设置页位置，也就是y的位置
    oled->iic_write_byte_func(
        oled->dev_addr, OLED_WRITE_CMD, 0x10 | ((x & 0xF0) >> 4));   // 设置X位置高4位
    oled->iic_write_byte_func(
        oled->dev_addr, OLED_WRITE_CMD, 0x00 | (x & 0x0F));   // 设置X位置低4位
}

#if MYLIB_USE_OLED_BUFFER_MODE
// 缓冲区模式下，下面这些函数才有效
///////////////////////////////////////////////////////////////////////////////

static void oled_write_data(oled_t* oled, uint8_t* data, uint8_t len)
{
    for (uint8_t i = 0; i < 128; i++) {
        OLED_WRITE_DATA_FUNC(oled, data[i]);
    }
}

// 更新函数，将缓冲区内容写入到oled设备
void oled_update(oled_t* oled)
{
    /*遍历每一页*/
    for (uint8_t j = 0; j < 8; j++) {
        /*设置光标位置为每一页的第一列*/
        oled_set_cursor(oled, j, 0);
        /*连续写入128个数据，将显存数组的数据写入到OLED硬件*/
        oled_write_data(oled, oled->buffer[j], 128);
    }
}

// 更新部分区域
void oled_update_area(oled_t* oled, uint8_t x, uint8_t y, uint8_t width, uint8_t height)
{
    /*参数检查，保证指定区域不会超出屏幕范围*/
    if (x > 127) {
        return;
    }   // 如果X大于127则打印无意义,故无需打印
    if (y > 63) {
        return;
    }   // 如果Y大于63则打印无意义,故无需打印

    if (x + width > 128) {
        width = 128 - x;
    }
    if (y + height > 64) {
        height = 64 - y;
    }

    /*遍历指定区域涉及的相关页*/
    /*(Y + Height - 1) / 8 + 1的目的是(Y + Height) / 8并向上取整*/
    for (uint8_t j = y / 8; j < (y + height - 1) / 8 + 1; j++) {
        /*设置光标位置为相关页的指定列*/
        oled_set_cursor(oled, j, x);
        /*连续写入width个数据，将显存数组的数据写入到OLED硬件*/

        oled_write_data(oled, &oled->buffer[j][x], width);
    }
}

// 清空缓冲区
void oled_clear(oled_t* oled) {}
// 清空缓冲区指定区域
void oled_clear_area(oled_t* oled, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {}
// 翻转缓冲区
void oled_reverse(oled_t* oled) {}
// 翻转缓冲区指定区域
void oled_reverse_area(oled_t* oled, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {}

#elif MYLIB_USE_OLED_DIRECT_MODE
// 直接模式下，下面这些函数才有效
///////////////////////////////////////////////////////////////////////////////
// 清空oled
void oled_clear(oled_t* oled)
{
    MYLIB_DEBUG_ASSERT(oled != NULL);
    uint8_t i, j;
    for (j = 0; j < 8; j++) {
        oled_set_cursor(oled, j, 0);
        for (i = 0; i < 128; i++) oled->iic_write_byte_func(oled->dev_addr, OLED_WRITE_DATA, 0x00);
    }
}

/**清除指定行列内容**
 *@param 	起始行/起始列
 *@param 	结束行/结束列
 **/
void oled_clear_line(oled_t* oled, uint8_t line1, uint8_t line2)
{
    for (uint8_t i = line1; i <= line2; i++) {
        for (uint8_t j = 1; j <= 16; j++) {
            oled_draw_char(oled, i, j, ' ');
        }
    }
}

void oled_clear_column(oled_t* oled, uint8_t column1, uint8_t column2)
{
    for (uint8_t i = column1; i <= column2; i++) {
        for (uint8_t j = 1; j <= 4; j++) {
            oled_draw_char(oled, j, i, ' ');
        }
    }
}

#endif
///////////////////////////////////////////////////////////////////////////////
// 绘制函数

// 绘制一个字符
void oled_draw_char(oled_t* oled, uint8_t line, uint8_t column, char ch)
{
    uint8_t i;
    oled_set_cursor(oled, (line - 1) * 2, (column - 1) * 8);   // 设置光标位置在上半部分
    for (i = 0; i < 8; i++) {
        OLED_WRITE_DATA_FUNC(oled, OLED_F8x16[ch - ' '][i]);   // 显示上半部分内容
    }
    oled_set_cursor(oled, (line - 1) * 2 + 1, (column - 1) * 8);   // 设置光标位置在下半部分
    for (i = 0; i < 8; i++) {
        OLED_WRITE_DATA_FUNC(oled, OLED_F8x16[ch - ' '][i + 8]);   // 显示下半部分内容
    }
}

/**OLED在指定位置显示字符串，单行显示，不自动换行**
 * @param		Line 起始行位置，范围：1~4
 * @param		Column 起始列位置，范围：1~16
 * @param		String 要显示的字符串，范围：ASCII可见字符
 * @retval		无
 */
void oled_draw_string(oled_t* oled, uint8_t Line, uint8_t Column, char* String)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i++) {
        oled_draw_char(oled, Line, Column + i, String[i]);
    }
}

/**OLED从起始行显示字符串，到结束行终止显示，可自动换行**
 *@ATTENTION 本函数跳过字符\r和字符\n，不会显示出来
 *@param 	起始行
 *@param 	显示的字符
 *@param 	结束行
 **/
void oled_draw_string_from_line_to_line(oled_t* oled, uint8_t startline, char* str, uint8_t endline)
{
    for (uint8_t i = 0, j = 1; str[i] != '\0'; i++)
        if (str[i] != '\r' && str[i] != '\n') {
            oled_draw_char(oled, startline, j++, str[i]);
            if (j >= 17 && startline < endline) {
                startline++;
                j = 1;
            }
            else if (j >= 17 && startline == endline)
                break;
        }
}

/**OLED从指定位置显示字符串，到结束行终止显示，可自动换行**
 *@ATTENTION	本函数跳过字符\r和字符\n，不会显示出来
 *@param		起始行
 *@param 	起始列
 *@param 	显示字符串
 *@param 	终止行
 **/
void oled_draw_string_from_point_to_line(oled_t* oled, uint8_t Line, uint8_t Column, char* String,
                                         uint8_t endline)
{
    uint8_t i, j;   // j用来保持字符串稳定增加
    for (i = 0, j = 0; String[j] != '\0'; j++) {
        if (String[j] != '\r' && String[j] != '\n') {
            oled_draw_char(oled, Line, Column + i, String[j]);
            i++;
        }
        if ((Column + i) == 17 && Line < endline) {
            Line += 1;
            Column = 1;
            i      = 0;
        }
        else if ((Column + i) == 17 && Line == endline)
            break;
    }
}

/**OLED显示数字（十进制，正数)**
 *@param		Line 起始行位置，范围：1~4
 *@param		Column 起始列位置，范围：1~16
 *@param		Number 要显示的数字，范围：0~4294967295
 *@param		Length 要显示数字的长度，范围：1~10
 *@retval	无
 **/
void OLED_ShowNum(oled_t* oled, uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
    for (uint8_t i = 0; i < Length; i++)
        oled_draw_char(oled, Line, Column + i, Number / pow32i(10, Length - i - 1) % 10 + '0');
}

/**OLED显示数字（十进制，带符号数）**
 *@param		Line 起始行位置，范围：1~4
 *@param		Column 起始列位置，范围：1~16
 *@param		Number 要显示的数字，范围：-2147483648~2147483647
 *@param		Length 要显示数字的长度，范围：1~10
 *@retval	无
 **/
void OLED_ShowSignedNum(oled_t* oled, uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
    uint8_t  i;
    uint32_t Number1;
    if (Number >= 0) {
        oled_draw_char(oled, Line, Column, '+');
        Number1 = Number;
    }
    else {
        oled_draw_char(oled, Line, Column, '-');
        Number1 = -Number;
    }
    for (i = 0; i < Length; i++)
        oled_draw_char(oled, Line, Column + i + 1, Number1 / pow32i(10, Length - i - 1) % 10 + '0');
}

/**OLED显示数字（十六进制，正数）**
 *@param		Line 起始行位置，范围：1~4
 *@param		Column 起始列位置，范围：1~16
 *@param		Number 要显示的数字，范围：0~0xFFFFFFFF
 *@param		Length 要显示数字的长度，范围：1~8
 *@retval	无
 **/
void oled_draw_hex_number(oled_t* oled, uint8_t Line, uint8_t Column, uint32_t Number,
                          uint8_t Length)
{
    uint8_t i, SingleNumber;
    for (i = 0; i < Length; i++) {
        SingleNumber = Number / pow32i(16, Length - i - 1) % 16;
        if (SingleNumber < 10)
            oled_draw_char(oled, Line, Column + i, SingleNumber + '0');
        else
            oled_draw_char(oled, Line, Column + i, SingleNumber - 10 + 'A');
    }
}

/**OLED显示数字（二进制，正数）**
 *@param		Line 起始行位置，范围：1~4
 *@param		Column 起始列位置，范围：1~16
 *@param		Number 要显示的数字，范围：0~1111 1111 1111 1111
 *@param		Length 要显示数字的长度，范围：1~16
 *@retval	无
 **/
void oled_draw_bin_number(oled_t* oled, uint8_t Line, uint8_t Column, uint32_t Number,
                          uint8_t Length)
{
    for (uint8_t i = 0; i < Length; i++)
        oled_draw_char(oled, Line, Column + i, Number / pow32i(2, Length - i - 1) % 2 + '0');
}


#if MYLIB_USE_OLED_BUFFER_MODE
// 绘制一个点
void oled_draw_point(oled_t* oled, uint8_t x, uint8_t y)
{
    /*参数检查，保证指定位置不会超出屏幕范围*/
    if (x > 127) {
        return;
    }
    if (y > 63) {
        return;
    }

    /*将显存数组指定位置的一个Bit数据置1*/
    oled->buffer[y / 8][x] |= 0x01 << (y % 8);
}

// 获取一个点的值
uint8_t oled_get_point(oled_t* oled, uint8_t x, uint8_t y)
{
    /*参数检查，保证指定位置不会超出屏幕范围*/
    if (x > 127) {
        return 0;
    }
    if (y > 63) {
        return 0;
    }

    /*判断指定位置的数据*/
    if (oled->buffer[y / 8][x] & 0x01 << (y % 8)) {
        return 1;   // 为1，返回1
    }

    return 0;   // 否则，返回0
}


// 绘制一条直线
void oled_draw_line(oled_t* oled, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {}
// 绘制一个矩形
void oled_draw_rectangle(oled_t* oled, uint8_t x, uint8_t y, uint8_t widtht, uint8_t height,
                         bool is_filled)
{}
// 绘制一个三角形
void oled_draw_triangle(oled_t* oled, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t x2,
                        uint8_t y2, bool is_filled)
{}
// 绘制一个圆
void oled_draw_circle(oled_t* oled, uint8_t x, uint8_t y, uint8_t radius, bool is_filled) {}
// 绘制一个椭圆
void oled_draw_ellipse(oled_t* oled, uint8_t x, uint8_t y, uint8_t a, uint8_t b, bool is_filled) {}
// 绘制一个弧
void oled_draw_arc(oled_t* oled, uint8_t x, uint8_t y, uint8_t radius, int16_t start_angle,
                   int16_t end_angle, bool is_filled)
{}

#endif
